package webuy

import (
	"bytes"
	"crypto/md5"
	"encoding/hex"
	"encoding/json"
	"errors"
	"fmt"
	"net/http"
	"sort"
	"strconv"
	"strings"
	"time"
)

const (
	UrlTesting    = "https://dailyopenapi.webuy.ai/gateway"
	UrlProduction = "https://openapi.webuy.ai/gateway"
	UrlStatic     = "http://cdn.webuy.ai"
)

type BaseRequest struct {
	AppId          string `json:"appId"`
	SupplierDockId string `json:"supplierDockId"`
	RandomStr      string `json:"randomStr"`
	Sign           string `json:"sign"`
	GmtRequest     int64  `json:"gmtRequest"`
}

type BaseResponse struct {
	Status       bool        `json:"status"`
	ResponseCode int         `json:"responseCode,omitempty"`
	Message      string      `json:"message,omitempty"`
	Entry        interface{} `json:"entry,omitempty"`
	Count        int         `json:"count,omitempty"`
}

type Requester interface {
	ApiPath() string
	RequestMethod() string
}

type Responser interface {
}

type Client struct {
	options Options
}

func NewClient(options ...Option) *Client {
	opts := NewOptions(options...)
	return &Client{
		options: opts,
	}
}

// Do
func (client *Client) Do(requester Requester, responser Responser) error {
	// struct to map for sign
	reqBuf, err := json.Marshal(requester)
	if err != nil {
		return err
	}
	m := make(map[string]interface{})
	err = json.Unmarshal(reqBuf, &m)
	if err != nil {
		return err
	}
	timestamp := time.Now().UnixNano() / 1e6
	m["appId"] = client.options.AppId
	//m["appSecret"] = client.options.AppSecret
	m["supplierDockId"] = client.options.SupplierDockId
	m["randomStr"] = strconv.FormatInt(timestamp, 10)
	m["gmtRequest"] = timestamp
	m["sign"] = Sign(m, client.options.AppSecret)

	//
	method := requester.RequestMethod()
	url := client.options.Url + requester.ApiPath()
	buf, err := json.Marshal(m)
	if err != nil {
		return err
	}
	body := bytes.NewBuffer(buf)
	req, err := http.NewRequest(method, url, body)
	if err != nil {
		return err
	}
	req.Header.Set("Content-Type", "application/json;charset=UTF-8")

	//
	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		return nil
	}
	defer resp.Body.Close()
	err = json.NewDecoder(resp.Body).Decode(responser)
	if err != nil {
		return err
	}
	return nil
}

func md5Sum(s string) string {
	buf := bytes.NewBufferString(s)
	h := md5.Sum(buf.Bytes())
	return hex.EncodeToString(h[:])
}

// Sign
func Sign(m map[string]interface{}, appSecret string) string {
	var keys []string
	for k, _ := range m {
		keys = append(keys, k)
	}
	sort.Strings(keys)
	sb := strings.Builder{}
	for k, v := range keys {
		if k != 0 {
			sb.WriteString("&")
		}
		sb.WriteString(v)
		sb.WriteString("=")
		//sb.WriteString(m[v])
		switch value := m[v].(type) {
		case uint8, int8, uint16, int16, uint, int, int32, int64, float32, float64:
			sb.WriteString(fmt.Sprint(value))
		case string:
			sb.WriteString(value)
		case []interface{}:
			p, err := json.Marshal(value)
			if err != nil {
				panic(err)
			}
			sb.Write(p)
		case nil:
			sb.WriteString("")
		default:
			panic(errors.New("switch type fail"))
		}
	}
	sb.WriteString("&key=")
	sb.WriteString(appSecret)
	s := md5Sum(sb.String())
	return strings.ToUpper(s)
}
